package com.wn.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wn.common.RedisConst;
import com.wn.common.enumeration.BusinessCode;
import com.wn.common.enumeration.MyAssert;
import com.wn.common.enumeration.UserStatus;
import com.wn.common.util.JwtUtil;
import com.wn.model.entity.User;
import com.wn.model.vo.LoginRequest;
import com.wn.model.vo.RegisterRequest;
import com.wn.model.vo.UserVo;
import com.wn.service.UserService;
import com.wn.mapper.UserMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
* @author KOO
* @description 针对表【t_user】的数据库操作Service实现
* @createDate 2024-12-10 17:17:56
*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
    implements UserService{
    @Autowired
    BCryptPasswordEncoder bCryptPasswordEncoder;
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    private JwtUtil jwtUtil;
    @Autowired
    private JavaMailSender mailSender;
    @Value("${spring.mail.username}")
    private String from;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public void sendEmail(String address) {

        User user = baseMapper.selectOne(Wrappers.lambdaQuery(User.class).eq(User::getEmail, address));
        //判断邮箱是否已经注册
        MyAssert.error( user!=null, BusinessCode.EMAIL_EXIST_ERROR);
        String text = RandomUtil.randomNumbers(4);

        //把消息封装到一个Map中，给MQ
        Map<String, Object> emailData = new HashMap<>();
        emailData.put("from", from);
        emailData.put("to", address);
        emailData.put("subject", "蜗牛书城：注册验证码");
        String context = "注册的验证码是：" + text + ", 该验证码15分钟之内有效！";
        emailData.put("content", context);
        // 发送消息到RabbitMQ队列中 参数1：队列名称 参数2：消息内容
        rabbitTemplate.convertAndSend("email.queue", emailData);


       /* //发送邮件========================================
        SimpleMailMessage mail = new SimpleMailMessage();
        mail.setFrom(from);//发件箱
        mail.setTo(address);  //收件箱
        mail.setSubject("蜗牛书城：验证码");//邮件主题
        String context = "注册的验证码是：" + text + ", 该验证码15分钟之内有效！";
        mail.setText(context);//生成的验证码
        //发送邮件
        mailSender.send(mail);*/
        //将数据存储在Redis中,时间设置为15分钟，邮箱地址是键
        redisTemplate.opsForValue().set(RedisConst.EMAIL_ADDRESS_PREFIX + ":" + address, text, RedisConst.EMAIL_CODE_EXPIRE, TimeUnit.MINUTES);
    }

    //手机登录
    @Override
    public UserVo smsLogin(LoginRequest params,HttpServletRequest request){
        //获取手机号
        String telephone = params.getTelephone();
        //从Redis中获取验证码
        String smsCode = (String) redisTemplate.opsForValue().get(RedisConst.SHORT_MESSAGE_SERVICE + ":" + telephone);
        //判断验证码是否过期
        MyAssert.error(Objects.isNull(smsCode), BusinessCode.INVALID_VERIFICATION);
        //判断验证码是否正确
        String code = params.getCode();
        MyAssert.error(!code.equals(smsCode), BusinessCode.VERIFICATION_ERROR);
        //删除验证码
        redisTemplate.delete(RedisConst.SHORT_MESSAGE_SERVICE + ":" + telephone);
        //根据手机号查询用户信息
        User user = baseMapper.selectOne(Wrappers.lambdaQuery(User.class).eq(User::getMobile, telephone));
        //如果用户存在，判断状态是否正常
        MyAssert.error(Objects.nonNull(user) && !user.getState().equals(UserStatus.ACTIVE.getKey()),
                BusinessCode.USER_FROZEN_ERROR);
        //如果用户不存在，创建新用户
        if (Objects.isNull(user)) {
            //创建默认密码：123
            String encode = bCryptPasswordEncoder.encode("123");
            user = new User();
            user.setMobile(telephone);  //手机号
            user.setPassword(encode);    //加密密码
            user.setAccount(telephone);   //账户名：手机号
            user.setNickname(telephone);
            user.setRealName(telephone);
            user.setState(UserStatus.ACTIVE.getKey());  //状态
            this.save(user);
        }
        //不为空则登录成功
        return createTokenAndUserVo(request, user);
    }

//注册============================
    @Override
    public UserVo register(RegisterRequest registerRequest, HttpServletRequest request) {
        //从Redis中获取验证码是否正确
        String redisCode = (String) redisTemplate.opsForValue()
                                .get(RedisConst.EMAIL_ADDRESS_PREFIX + ":" + registerRequest.getEmail());
        //判断验证码是否过期
        MyAssert.error(Objects.isNull(redisCode), BusinessCode.INVALID_VERIFICATION);
        //判断验证码是否正确
        MyAssert.error(!redisCode.equals(registerRequest.getValidCode()), BusinessCode.VERIFICATION_ERROR);
        //判断用户名是否重复
        User tempUser = baseMapper.selectOne(Wrappers.lambdaQuery(User.class).eq(User::getAccount, registerRequest.getAccount()));
        MyAssert.error( tempUser != null  , BusinessCode.USER_IS_EXIST);
        //判断手机是否重复
        tempUser = baseMapper.selectOne(Wrappers.lambdaQuery(User.class).eq(User::getMobile, registerRequest.getMobile()));
        MyAssert.error( tempUser != null , BusinessCode.MOBILE_IS_EXIST);

        //将注册信息复制到user对象中
        User newUser = new User();
        BeanUtils.copyProperties(registerRequest, newUser);

        this.save(newUser);

        //默认已经登录
        return createTokenAndUserVo(request, newUser);


    }


    @Override
    public UserVo login(LoginRequest param, HttpServletRequest request) {
        //验证验证码
        String code = (String) redisTemplate.opsForValue().get(param.getKey());
        MyAssert.error(code==null, BusinessCode.INVALID_VERIFICATION);
        MyAssert.error(!code.equalsIgnoreCase(param.getCode()), BusinessCode.VERIFICATION_ERROR);
        redisTemplate.delete(param.getKey());

        //判断用户名是否存在
        LambdaQueryWrapper<User> queryWrapper =
                Wrappers.lambdaQuery(User.class).eq(User::getAccount, param.getAccount());
        User user = baseMapper.selectOne(queryWrapper);
        MyAssert.error(user==null, BusinessCode.USER_NOT_EXIST);

        //判断密码是否正确
        boolean matches = bCryptPasswordEncoder.matches(param.getPassword(), user.getPassword());
        MyAssert.error(!matches, BusinessCode.PASSWORD_ERROR);

        //判断是否用户可用
        MyAssert.error(user.getState()!= UserStatus.ACTIVE.getKey(), BusinessCode.USER_FROZEN_ERROR);

        //1.生成Token
        return createTokenAndUserVo(request, user);

    }

    /**
     * 通过ID查询1个用户
     * @param id
     * keyGenerator = "keyGenerator" (自定义的缓存key生成策略)
     */
    @Override
    @Cacheable(cacheNames = RedisConst.USER_DETAIL, keyGenerator = "keyGenerator")
    public UserVo findById(Long id) {
        //调用数据层查询
        User user = baseMapper.selectById(id);
        //复制属性
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(user, userVo);
        return userVo;
    }

    /**
     * 生成Token，并且封装UserVo对象
     */
    private UserVo createTokenAndUserVo(HttpServletRequest request, User user) {
        //1.生成Token
        HashMap<String, Object> data = new HashMap<>();
        data.put("userId", user.getId());
        data.put("userName", user.getAccount());
        //为了安全，判断IP地址是否相同
        data.put("ip", jwtUtil.getRequestIP(request));
        String token = jwtUtil.createToken(data);

        String key = RedisConst.USER_TOKEN_PREFIX + ":" + user.getId();
        redisTemplate.opsForValue().set(key, token, Duration.ofDays(1));
        //复制属性：源对象，目标对象
        UserVo userVo = BeanUtil.copyProperties(user, UserVo.class);
        userVo.setToken(token);

        return userVo;
    }



    @Override
    @SneakyThrows
    public Map<String, String> getCode() {
        //2) 创建缓存图片：指定宽
        int width = 110, height = 45;
        //参数3：图片的类型，使用RGB类型
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        //3) 获取画笔对象
        Graphics graphics = image.getGraphics();
        //4) 设置画笔颜色，并且填充矩形区域
        graphics.setColor(Color.WHITE);
        graphics.fillRect(0, 0, width, height);
        //创建一个StringBuider对象
        StringBuilder sb = new StringBuilder();
        //5) 从字符数组中随机得到字符
        String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        char[] arr = characters.toCharArray();
        Random random = new Random();
        for (int i = 0; i < 4; i++) {
            //随机从0到长度之间去取索引
            int index = random.nextInt(arr.length);
            //得到1个字符
            char c = arr[index];
            sb.append(c);
            //6) 设置字体，大小为22，设置字的颜色随机
            graphics.setFont(new Font(null, Font.BOLD + Font.ITALIC, 22));
            graphics.setColor(getColor());
            //7) 将每个字符画到图片，位置：5+(i*23), 25
            //将字符转成字符串
            graphics.drawString(String.valueOf(c), 5 + (i * 23), 25);
        }
        //8) 画干扰线8条线，线的位置是随机的，x范围在width之中，y的范围在height之中。
        for (int i = 0; i < 10; i++) {
            graphics.setColor(getColor());
            int x1 = random.nextInt(width);
            int y1 = random.nextInt(height);
            int x2 = random.nextInt(width);
            int y2 = random.nextInt(height);
            graphics.drawLine(x1, y1, x2, y2);
        }
        //9) 将缓存的图片输出到响应输出流中
        //参数1：要输出的缓存图片，参数2：格式，参数3：输出到哪个流中
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, "jpeg", outputStream);
        //随机生成一个key，前面加上code::
        String key = RedisConst.USER_VALIDATE_CODE + ":" + UUID.randomUUID().toString().replace("-", "");
        //得到验证码，将生成的字符串放到Redis中，键随机生成，10分钟过期
        redisTemplate.opsForValue().set(key, sb.toString(), Duration.ofMinutes(10));
        log.info("生成的验证码为：{}，Redis中的key是：{}", sb, key);
        Base64.Encoder encoder = Base64.getEncoder();
        String base64Img = "data:image/jpeg;base64," + encoder.encodeToString(outputStream.toByteArray());
        //返回的数据包含验证码的key和图片
        Map<String, String> data = new HashMap<>();
        data.put("key", key);  //redis中键
        data.put("base64Img", base64Img);  //图片
        return data;
    }

    //写一个方法随机获取颜色
    private Color getColor() {
        Random random = new Random();
        //红，绿，蓝取值0-255，随机生成 0-255之间数
        int r = random.nextInt(256);
        int g = random.nextInt(256);
        int b = random.nextInt(256);
        return new Color(r, g, b);
    }


}




